package general;

import general.CPA.CPA;
import general.CPA.DynamicCPA;
import general.conflictset.ArrayConflictSet;
import general.conflictset.ConflictSet;
import general.constraints.VariableConstraints;
import general.messages.BacktrackMessage;
import general.messages.CPAMessage;
import general.messages.ConflictMessage;
import general.messages.FailMessage;
import general.messages.Message;
import general.messages.RestoreMessage;
import general.messages.SplitMessage;
import general.messages.SuccessMessage;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.Vector;

import algorithms.TwoIntegers;


/**
 * ChannelAgent - an agent that uses channels for communication
 * the agent is designed to communicate with a given number of other agents,
 * and has a unique ID number 
 */
public abstract class ChannelAgent extends Thread{
	public static final byte SEND_CPA = 31;
	public static final byte SEND_CONFLICTSET = 32;
	public static final byte SEND_RESTORE = 33;
	public static final byte SEND_FAIL = 34;
	public static final byte SEND_SUCCESS = 35;
	public static final byte SEND_SPLIT = 36;
	public static final byte SEND_BACKTRACK = 37;
	public static final byte SEND_DYNAMICCPA = 38;
	public static final byte SEND_SUCCESSDYNAMIC = 39;

	public static final byte END_CHAR = 51;
	public static final byte FINISH_CHAR = 50;

	public static final boolean LOG_FAIL = false;
	public static final boolean LOG_SUCCESS = false;
	public static final boolean LOG_SENDNEXT = false;
	public static final boolean LOG_BACKTRACK = false;
	public static final boolean LOG_STARTRUN = false;
	public static final boolean LOG_FINISHRUN = false;
	public static final boolean LOG_INIT = false;
	public static final boolean LOG_CREATENEWSP = false;
	public static final boolean LOG_SPLIT = false;
	public static final boolean LOG_HANDLE_CPA = false;
	public static final boolean LOG_HANDLE_SUCCESS = false;
	public static final boolean LOG_HANDLE_CONFLICTS = false;
	public static final boolean LOG_HANDLE_FAIL = false;
	public static final boolean LOG_HANDLE_BACKTRACK = false;
	public static final boolean LOG_HANDLE_SPLIT = false;
	public static final boolean LOG_HANDLE_RESTORE = false;
	private static final boolean LOG_CONNECTIONSTATUS = false;
	private static final boolean LOG_CONNECTING = false;
	private static final boolean LOG_CONNECTIONCREATE = false;
	private static final boolean LOG_CONNTIONSDONE = false;
	private static final boolean LOG_GOTCONNECTION = false;
	private static final boolean LOG_INVALIDREAD = false;
	//	private static boolean LOG_READFROMBUFFER = false;

	private PrintWriter logger;

	protected int n;
	protected byte id;
	private SocketChannel[] scs;
	private ServerSocketChannel ssc;
	private Selector s;
	//	private Selector s2;
	private Vector<Message> messages;
	protected TreeMap<TwoIntegers, Integer> messageStatus;
	protected HashMap<Message, Integer> waitTimes;
	private boolean startedListening;
	private boolean sentConnections;
	private boolean receivedConnections;
	private boolean openedSelector;
	private long numMessages = 0;
	private long NCCs = 0;

	private CPA cpaProto;
	protected VariableConstraints constraints;


	private int delay;
	private Timer messageSender;

	protected int numOpenChannels;

	/**
	 * creates an instance of ChannelAgent
	 * @param id id of this agent
	 * @param n total number of agents in the system
	 */
	public ChannelAgent(byte id, int n, VariableConstraints constraints, CPA cpaProto, int delay) {
		this.n = n;
		this.id = id;
		this.constraints = constraints;
		this.cpaProto = cpaProto;
		if (delay!=0){
			this.messageSender = new Timer();
		}
		this.delay = delay;
		scs = new SocketChannel[n];
		messages = new Vector<Message>();
		messageStatus = new TreeMap<TwoIntegers, Integer>();
		waitTimes = new HashMap<Message, Integer>();
		numOpenChannels=0;

		File logFile = new File(id+".txt");
		try {
			//			logFile.delete();
			//			logFile.createNewFile();
			logger = new PrintWriter(logFile);
		} catch (IOException e) {
			e.printStackTrace();
		}

		startedListening = false;
		sentConnections = false;
		receivedConnections = false;
		openedSelector = false;
	}

	/**
	 * Construct a new CPA with the given parameters
	 * @param n total number of agent
	 * @param id ID of the new CPA
	 * @param creator creator of this CPA
	 * @return the CPA instance that was created
	 */
	protected CPA getNewCPA(int n, int id, int creator){
		return cpaProto.createNew(n, id, creator);
	}

	/**
	 * Constructs a new CPA with the given parameters (used when receiving a CPA message) 
	 * @param id CPA id
	 * @param steps steps that this CPA did without returning to its creator
	 * @param creator creator of this CPA
	 * @param cpa byte array representing the CPA (-1 for unassigned values)
	 * @return
	 */
	protected CPA getNewCPA(int id, int steps, int creator, byte[] cpa, byte[] order){
		return cpaProto.createNew(id, steps, creator, cpa, order);
	}

	/**
	 * prints a line of text to the log file
	 * @param line line of text to insert to the log file
	 */
	protected void log(String line, boolean print){
		if (print){
			String time = GregorianCalendar.getInstance().getTime().toString();
			String output = "["+time + "] - Agent " + id + ": " +line;
			logger.println(output);
			logger.flush();
			System.out.println(output);
		}
	}

	/**
	 * sends a message with a given type to the remote agent
	 * @param type message type
	 * @param msg message content
	 * @param agent destination
	 */
	private void sendMessage(byte type, byte[] msg, final int agent){
		numMessages++;
		final ByteBuffer f = ByteBuffer.allocate(msg.length+1+1);
		f.put(type);
		f.put(msg);
		f.put(END_CHAR);
		f.rewind();
		if (delay!=0){
			messageSender.schedule(new TimerTask(){
				public void run(){
					sendBufferContent(agent, f);
				}
			}, delay);
		} else {
			sendBufferContent(agent, f);
		}

	}

	/**
	 * sends the content of a buffer to an agent
	 * @param agent the agent to send to
	 * @param buf the buffer to send
	 */
	private void sendBufferContent(int agent, ByteBuffer buf){
		//		try{
		SelectionKey sk = scs[agent].keyFor(s);
		sk.attach(new WriteOperation(agent, buf));
		sk.interestOps(SelectionKey.OP_WRITE | SelectionKey.OP_READ);
		while(!readFromBuffer(true)){
			//			LOG_READFROMBUFFER = true;
			//			System.err.println("["+new Date() + "] - Agent " + id+ ": Attempting to send to " + agent);
			yield();
		}
		//		System.err.println("["+new Date() + "] - Agent " + id+ ": Finishd sending to " + agent);
		//		LOG_READFROMBUFFER = false;
		//			int numBytes = buf.capacity();
		//			int bytesWritten = scs[agent].write(buf); 
		//			while (bytesWritten<numBytes){
		//				LOG_READFROMBUFFER = true;
		//				readFromBuffer(false);	//emptying buffer
		//				log("Error - message not fully sent to agent " + agent + ", Sending again (sent "+bytesWritten + "/" +numBytes+")", true);
		//				System.err.println("Agent "+id+": Error - message not fully sent to agent " + agent + ", Sending again (sent "+bytesWritten + "/" +numBytes+")");
		//				scs[agent].configureBlocking(true);
		//				bytesWritten = scs[agent].write(buf);
		//				System.err.println("Recovered - Sent succesfully");
		//				LOG_READFROMBUFFER = false;
		//				scs[agent].configureBlocking(false);
		//			}
		//		}catch (IOException e) {
		//			e.printStackTrace();
		//		}	
	}

	/**
	 * sends a CPA to an agent
	 * @param cpa the CPA to sent
	 * @param agent the remote agent that receives the CPA
	 */
	protected void send(CPA cpa, byte agent){
		sendMessage(cpa.getType(), cpa.getBytes(), agent);
	}

	/**
	 * sends a CPA to an agent
	 * @param cpa the CPA to sent
	 * @param agent the remote agent that receives the CPA
	 */
	protected void sendBacktrack(TwoIntegers id, byte agent){
		ByteBuffer b = ByteBuffer.allocate(4+4);
		b.putInt(id.int1);
		b.putInt(id.int2);
		sendMessage(SEND_BACKTRACK, b.array(), agent);
	}

	/**
	 * sends a conflictSet to an agent 
	 * @param conflicts the conflictSet to send to the remote agent
	 * @param h the remote agent that receives the conflict set
	 */
	protected void send(ConflictSet conflicts, CPA p, byte h) {
		ByteBuffer b = ByteBuffer.allocate(n+4+4);
		b.putInt(p.getId().int1);
		b.putInt(p.getId().int2);
		b.put(conflicts.getBytes());
		sendMessage(SEND_CONFLICTSET, b.array(), h);
	}

	/**
	 * sends a restore agent to a remote agent
	 * @param i the agent to send the message to
	 */
	protected void sendRestore(byte i) {
		sendMessage(SEND_RESTORE, new byte[0], i);
	}
	/**
	 * sends a split message to a remote agent
	 * @param splitter the remote agent to send the message to
	 */
	protected void sendSplit(TwoIntegers id, int splitter) {
		ByteBuffer b = ByteBuffer.allocate(4+4);
		b.putInt(id.int1);
		b.putInt(id.int2);
		sendMessage(SEND_SPLIT, b.array(), splitter);
	}
	/**
	 * sends a fail message to a remote agent
	 * @param i the remote agent to send the message to
	 */
	protected void sendFail(byte i) {
		sendMessage(SEND_FAIL, new byte[0], i);
	}
	/**
	 * sends a success message to a remote agent
	 * @param i the remote agent to send the message to
	 */
	protected void sendSuccess(CPA p, byte i) {
		if (p instanceof DynamicCPA){
			sendMessage(SEND_SUCCESSDYNAMIC, p.getBytes(), i);
		} else {
			sendMessage(SEND_SUCCESS, p.getBytes(), i);
		}
	}


	//TODO
	protected void sendEndMessage(int i) {
		sendMessage(FINISH_CHAR, new byte[0], i);
	}


	protected boolean readFromBuffer(boolean blocking){
		boolean ans = false;
		try {
			int num = 0;
			if (blocking){
				num = s.select();
			} else {
				num = s.selectNow();
			}
			if (num!=0){
				for (SelectionKey k: s.selectedKeys()){
					if (k.isWritable()){
						WriteOperation wo = (WriteOperation)(k.attachment());
						int bytesWritten = scs[wo.agent].write(wo.buf);
						if (bytesWritten == wo.buf.capacity()){
							k.interestOps(SelectionKey.OP_READ);
							ans = true;
							//							if (LOG_READFROMBUFFER) System.err.println("Agent " + id + ": written to buffer - " + bytesWritten + " bytes to agent " + wo.agent);
						} else {
							//							System.err.println("["+new Date() +"] - Agent " + id + ": Failed to send again ("+bytesWritten+"/"+wo.buf.capacity()+")");
						}
					}
					if (k.isReadable()){
						int i = ((WriteOperation)(k.attachment())).agent;
						ByteBuffer f = ByteBuffer.allocate(8192);
						int bytesRead = scs[i].read(f);
						f.rewind();
						//						if (LOG_READFROMBUFFER) System.err.println("Agent " + id + ": reading from buffer - " + bytesRead + " bytes from agent " + i);
						while (bytesRead>0){
							byte type = f.get();
							bytesRead--;
							switch (type) {
							case SEND_CONFLICTSET:
								bytesRead = receiveConflictSet(f, i, bytesRead);
								break;
							case SEND_CPA:
								bytesRead = receiveCpa(f, i, bytesRead);
								break;
							case SEND_DYNAMICCPA:
								bytesRead = receiveCpaDynamic(f, i, bytesRead);
								break;
							case SEND_BACKTRACK:
								bytesRead = receiveBacktrack(f, i, bytesRead);
								break;
							case SEND_RESTORE:
								messages.add(new RestoreMessage());
								f.get();		//discard end char
								bytesRead--;	//read 1 byte
								break;
							case SEND_SPLIT:
								bytesRead = receiveSplit(f, i, bytesRead);
								break;
							case SEND_FAIL:
								messages.removeAllElements();
								messages.add(new FailMessage());
								f.get();		//discard end char
								bytesRead--;	//read 1 byte
								break;
							case SEND_SUCCESS:
								bytesRead = receiveSuccess(f, i, bytesRead);
								break;
							case SEND_SUCCESSDYNAMIC:
								bytesRead = receiveSuccessDynamic(f, i, bytesRead);
								break;
							case FINISH_CHAR:
								messages.add(new EmptyMessage());
								//								log("Got end message from " + i,true);
								numOpenChannels--;
								f.get();		//discared end cahr
								bytesRead--;	//read 1 byte
								//k.cancel();
								break;
							default:
							{
								log("Unknown message type detected " + type, true);
								System.err.println("Agent "+id+": Unknown message type detected " + type);
								break;
							}
							}
						}	
					}
				}
				s.selectedKeys().clear();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return ans;
	}


	/**
	 * receives a message from any remote agent connected to this one
	 * this method blocks until a message is available
	 * the methods will store all of the incoming messages and move forward only one of them
	 * @return the received Message
	 */
	public Message receive(){
		if (messages.isEmpty()) readFromBuffer(true);
		else readFromBuffer(false); 
		return messages.remove(0);
	}

	private void updateMessageStatus(TwoIntegers id, Message m){
		Integer num = messageStatus.remove(id);
		if (num == null){
			messageStatus.put(id, 1);
		} else {
			messageStatus.put(id, (num.intValue()+1));
		}
		waitTimes.put(m, messageStatus.size());
	}
	
	private int discardIncompleteMessages(ByteBuffer f, int bytesRead){
		log("Error, no endchar detected, backtracking buffer", true);
		System.err.println("Agent "+id+": Error, no endchar detected, backtracking buffer");
		byte b = f.get();
		while (b!=SEND_CPA && b!=SEND_CONFLICTSET && b!=SEND_RESTORE && b!=SEND_FAIL && b!=SEND_SUCCESS &&
				b!=SEND_SPLIT && b!=SEND_BACKTRACK && b!=SEND_DYNAMICCPA && b!=SEND_SUCCESSDYNAMIC){
			bytesRead--;
			b = f.get();
		}
		f.position(f.position()-1);
		return bytesRead;
	}

	//TODO
	private int receiveBacktrack(ByteBuffer f, int i, int bytesRead){
		if (bytesRead<4+4+1) {
			bytesRead = transgerBuffer(f, scs[i], bytesRead);
		}
		int cpaId = f.getInt();
		int cpaCreator = f.getInt();
		bytesRead -= 4;
		bytesRead -= 4;

		byte last = f.get();
		bytesRead --;
		if (last!=END_CHAR){
			bytesRead+=4;
			bytesRead+=4;
			bytesRead++;
			f.position(f.position()-4-4-1);
			bytesRead = discardIncompleteMessages(f, bytesRead);
		} else {
			TwoIntegers id = new TwoIntegers(cpaId, cpaCreator);
			Message m = new BacktrackMessage(id);
			updateMessageStatus(id, m);
			messages.add(m);
		}
		return bytesRead;
	}
	//TODO
	private int receiveSplit(ByteBuffer f, int i, int bytesRead){
		if (bytesRead<4+4+1) {
			bytesRead = transgerBuffer(f, scs[i], bytesRead);
		}
		int cpaId = f.getInt();
		int cpaCreator = f.getInt();
		bytesRead -= 4;
		bytesRead -= 4;

		byte last = f.get();
		bytesRead --;
		if (last!=END_CHAR){
			bytesRead+=4;
			bytesRead+=4;
			bytesRead++;
			f.position(f.position()-4-4-1);
			bytesRead = discardIncompleteMessages(f, bytesRead);
		} else {
			TwoIntegers id = new TwoIntegers(cpaId, cpaCreator);
			Message m = new SplitMessage(id);
			updateMessageStatus(id, m);
			messages.add(m);
		}
		return bytesRead;
	}
	//TODO
	private int receiveConflictSet(ByteBuffer f, int i, int bytesRead){	
		if (bytesRead<n+4+4+1) {
			bytesRead = transgerBuffer(f, scs[i], bytesRead);
		}
		byte[] conflicts = new byte[n];
		int num1 = f.getInt();
		int num2 = f.getInt();
		f.get(conflicts);
		
		bytesRead -= 4;
		bytesRead -= 4;
		bytesRead -= n;

		byte last = f.get();
		bytesRead --;
		if (last!=END_CHAR){
			bytesRead+=4;
			bytesRead+=4;
			bytesRead+=n;
			bytesRead++;
			f.position(f.position()-n-4-4-1);
			bytesRead = discardIncompleteMessages(f, bytesRead);
		} else {
			TwoIntegers id = new TwoIntegers(num1, num2);
			Message m = new ConflictMessage(new ArrayConflictSet(conflicts), id);
			updateMessageStatus(id, m);
			messages.add(m);
		}
		return bytesRead;
	}



	//TODO
	private int receiveCpa(ByteBuffer f, int i, int bytesRead){
		if (bytesRead<n+4+4+4+1) {
			bytesRead = transgerBuffer(f, scs[i], bytesRead);
		}
		int cpaId = f.getInt();
		int creator = f.getInt();
		int cpaSteps = f.getInt();
		byte[] cpa = new byte[n];
		f.get(cpa);
		bytesRead -= 4;
		bytesRead -= 4;
		bytesRead -= 4;
		bytesRead -= n;

		byte last = f.get();
		bytesRead --;
		if (last!=END_CHAR){
			bytesRead+=4;
			bytesRead+=4;
			bytesRead+=4;
			bytesRead+=n;
			bytesRead++;
			f.position(f.position()-n-4-4-4-1);
			bytesRead = discardIncompleteMessages(f, bytesRead);
		} else {
			TwoIntegers id = new TwoIntegers(cpaId, creator);
			Message m = new CPAMessage(getNewCPA(cpaId, cpaSteps, creator, cpa, null));
			updateMessageStatus(id, m);
			messages.add(m);
		}
		return bytesRead;
	}
	//TODO
	private int receiveCpaDynamic(ByteBuffer f, int i, int bytesRead){
		if (bytesRead<n+n+4+4+4+1) {
			bytesRead = transgerBuffer(f, scs[i], bytesRead);
		}
		int dcpaId = f.getInt();
		int dcreator = f.getInt();
		int dcpaSteps = f.getInt();
		byte[] dcpa = new byte[n];
		byte[] dorder = new byte[n];
		f.get(dcpa);
		f.get(dorder);
		bytesRead -= 4;
		bytesRead -= 4;
		bytesRead -= 4;
		bytesRead -= n;
		bytesRead -= n;

		byte last = f.get();
		bytesRead --;
		if (last!=END_CHAR){
			bytesRead+=4;
			bytesRead+=4;
			bytesRead+=4;
			bytesRead+=n;
			bytesRead+=n;
			bytesRead++;
			f.position(f.position()-n-n-4-4-4-1);
			bytesRead = discardIncompleteMessages(f, bytesRead);
		} else {
			TwoIntegers id = new TwoIntegers(dcpaId, dcreator);
			Message m = new CPAMessage(getNewCPA(dcpaId, dcpaSteps, dcreator, dcpa, dorder));
			updateMessageStatus(id, m);
			messages.add(m);
		}
		return bytesRead;
	}
	//TODO
	private int receiveSuccess(ByteBuffer f, int i, int bytesRead){
		if (bytesRead<n+4+4+4+1) {
			bytesRead = transgerBuffer(f, scs[i], bytesRead);
		}
		f.getInt();
		f.getInt();
		f.getInt();
		byte[] dresult = new byte[n];
		f.get(dresult);
		bytesRead -= 4;
		bytesRead -= 4;
		bytesRead -= 4;
		bytesRead -= n;

		byte last = f.get();
		bytesRead --;
		if (last!=END_CHAR){
			bytesRead+=4;
			bytesRead+=4;
			bytesRead+=4;
			bytesRead+=n;
			bytesRead++;
			f.position(f.position()-n-4-4-4-1);
			bytesRead = discardIncompleteMessages(f, bytesRead);
		} else {
			messages.removeAllElements();
			messages.add(new SuccessMessage(dresult));
		}
		return bytesRead;
	}
	//TODO
	private int receiveSuccessDynamic(ByteBuffer f, int i, int bytesRead){
		if (bytesRead<n+n+4+4+4+1) {
			bytesRead = transgerBuffer(f, scs[i], bytesRead);
		}
		f.getInt();
		f.getInt();
		f.getInt();
		byte[] dresult = new byte[n];
		byte[] ddorder = new byte[n];
		f.get(dresult);
		f.get(ddorder);
		bytesRead -= 4;
		bytesRead -= 4;
		bytesRead -= 4;
		bytesRead -= n;
		bytesRead -= n;

		byte last = f.get();
		bytesRead --;
		if (last!=END_CHAR){
			bytesRead+=4;
			bytesRead+=4;
			bytesRead+=4;
			bytesRead+=n;
			bytesRead+=n;
			bytesRead++;
			f.position(f.position()-n-n-4-4-4-1);
			bytesRead = discardIncompleteMessages(f, bytesRead);
		} else {
			messages.removeAllElements();
			messages.add(new SuccessMessage(dresult));
		}
		return bytesRead;
	}


	/**
	 * In case the original buffer wasn't big enough to read everything from the channel, this method
	 * should be used to insert the leftover into it 
	 * @param f original buffer
	 * @param channel channel to read from
	 * @param bytesLeft bytes left on the buffer - to be read from the buffer 
	 * @return the number of bytes left on the buffer
	 */
	private int transgerBuffer(ByteBuffer f, SocketChannel channel,int bytesLeft){
		byte[] remaining = new byte[bytesLeft];
		f.get(remaining);
		f.clear();
		f.put(remaining);
		int bytesRead = 0;
		try {
			bytesRead = channel.read(f);
		} catch (IOException e) {
			e.printStackTrace();
		}
		f.rewind();
		return bytesRead + bytesLeft;
	}

	/**
	 * closes the open streams and channels
	 */
	public void close(){
		try {
			s.close();
			//			s2.close();
			for (int i=0; i<scs.length; i++){
				if (scs[i]!=null){
					scs[i].close();
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		logger.close();
		killTimer();
	}

	/**
	 * Kills the message sender timer thread
	 */
	public void killTimer(){
		if (delay!=0)
			messageSender.cancel();
	}

	/**
	 * starts a new thread that waits for connection requests from other agents and terminates when
	 * all of the other agents are connected
	 */
	public void getConnections(){
		new Thread(){
			@Override
			public void run() {
				recieveConnections();
			}
		}.start();
	}

	/***
	 * listens on the ServerSocketChannel to get connections from other agents
	 * this method waits until it receives connection requests from all of the agents with higher ID then this one
	 */
	private void recieveConnections(){
		try {
			List<SocketChannel> l = new LinkedList<SocketChannel>();
			for (int i=n-1; i>id; i--){
				l.add(ssc.accept());
				log("Got connection #" + (n - i), LOG_GOTCONNECTION);
			}
			ssc.close();
			mapConnections(l);
		} catch (IOException e) {
			e.printStackTrace();
		}
		receivedConnections = true;
	}

	/**
	 * Maps the connections which this agent received to the remote agent that sent the connection request
	 * the mapping is done by busy waiting for the ID of the agent to be sent through the socket.
	 * @param l the list of connections to map
	 */
	private void mapConnections(List<SocketChannel> l){
		while (!l.isEmpty()){
			for (SocketChannel sc: l){
				try {
					sc.configureBlocking(false);
					ByteBuffer bb = ByteBuffer.allocate(8192);
					int readBytes = sc.read(bb);
					if (readBytes < 1) {
						log("Read " + readBytes + " bytes, this shouldnt happen", LOG_INVALIDREAD);
						yield();
					} else if (readBytes > 1){
						log("Read " + readBytes + " bytes, this shouldnt happen", LOG_INVALIDREAD);
						yield();
					} else {
						byte socketAgent = bb.get(0);
						log("Recieved connection from agent " + socketAgent, LOG_GOTCONNECTION);
						synchronized (scs) {
							scs[socketAgent] = sc;
						}
					}

				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			for (int i=0; i<n ; i++) if (scs[i]!=null){
				l.remove(scs[i]);
			}
		}
		log("Done getting connections", LOG_CONNTIONSDONE);
	}

	/**
	 * Opens and binds a ServerSocketChannel
	 * @return the port which the channel listens to
	 */
	public int startListening(){
		try {
			ssc = ServerSocketChannel.open();
			ssc.socket().bind(null);
			startedListening = true;
			return ssc.socket().getLocalPort();
		} catch (IOException e) {
			e.printStackTrace();
			return -1;
		}
	}

	/**
	 * Creates connections to the other agents with IDs lower then this agent's ID
	 * After conneting, it sends a single byte containing the agent's ID to identify the connection
	 * @param ports the Ports which the other agents listens to.
	 */
	public void createConnections(int[] ports){
		log("Creating connections", LOG_CONNECTIONCREATE);
		for (int i=0; i<id; i++){
			boolean connectionOpen = false;
			while (!connectionOpen){
				try {
					log("Connecting to port " + ports[i], LOG_CONNECTING);
					SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", ports[i]));
					sc.configureBlocking(false);
					sc.write(ByteBuffer.wrap(new byte[]{id}));
					log("Sent my ID to agent " + i,LOG_CONNECTING);
					synchronized (sc) {
						scs[i] = sc;
					}
					connectionOpen = true;
				} catch (UnknownHostException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		sentConnections = true;
	}

	/**
	 * opens a selector and submits all of the open channels to it
	 */
	public void openSelector(){
		//		LOG_READFROMBUFFER = false;

		boolean connectionOpen = false;
		while(!connectionOpen){
			try {
				s = Selector.open();
				//				s2 = Selector.open();
				for (int i=0; i<n; i++) if (i!=id){
					scs[i].register(s, SelectionKey.OP_READ, new WriteOperation(i, null));
					numOpenChannels++;
					//					scs[i].register(s2, 0, new Integer(i));
				}
				connectionOpen = true;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		openedSelector = true;
	}

	/**
	 * 
	 * @return true when the agent is ready to receive connections
	 */
	public boolean isReadyForConnections() {
		return startedListening;
	}

	/**
	 * 
	 * @return true when all of the connections are made and the agent is ready to start the selector
	 */
	public boolean isReadyForSelector(){
		return receivedConnections && sentConnections;
	}

	/**
	 * 
	 * @return true after all channels are registered to the selector
	 */
	public boolean isReadySelector(){
		return openedSelector;
	}

	/**
	 * prints all of the active connections to this agent
	 */
	public void printStatus(){
		for (int i=0; i<n; i++){
			if (i!=id){
				try{
					log("Status - " + i + " - " + scs[i].toString(), LOG_CONNECTIONSTATUS);
				}catch (Exception e) {
					log("Status - " + i + " - Empty", LOG_CONNECTIONSTATUS);
				}
			}
		}
	}

	/**
	 * @return the total number of sent messages
	 */
	public long getNumMessages() {
		return numMessages;
	}

	/**
	 * Checks a constraint for a value and a different variable
	 * Returns true if there is a constraint between (me, myValue) and (other, otherValue)
	 * @param myValue the Value of this agent's variable
	 * @param other	the other Variable
	 * @param otherVal the Value of the other Variable
	 * @return True - if there is a constraint, False - otherwise
	 */
	protected boolean check(byte myValue, byte other, byte otherVal) {
		if (NCCs % 10000000 == 0 && NCCs>0){
			log("Reached " + NCCs +" NCCs", true);
		}
		NCCs++;
		return !constraints.hasConstraints(other, myValue, otherVal);
	}

	/**
	 * @return the total number of NCCs in this agent 
	 */
	public long getNCCs() {
		return NCCs;
	}

	public byte getAgentId() {
		return id;
	}

	public abstract void run();
}
